package com.glintt.sinnerg.web.service;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;

import com.glintt.sinnerg.web.dto.Person;

@Component
public class LdapService {

    private static final String ATTR_SAM_ACCOUNT_NAME = "sAMAccountName";
    private static final String ATTR_NAME = "name";
    private static final String ATTR_MOBILE = "mobile";
    private static final String ATTR_TELEPHONE_NUMBER = "telephoneNumber";
    private static final String ATTR_MAIL = "mail";
    private static final String ATTR_THUMBNAIL_PHOTO = "thumbnailPhoto";
    private static final String ATTR_PHOTO = "photo";

    private static final String[] SEARCHABLE_ATTRS = { ATTR_NAME, ATTR_MAIL, ATTR_MOBILE, ATTR_TELEPHONE_NUMBER };
    private static final String[] SEARCH_RETURN_ATTRS = { ATTR_NAME, ATTR_MAIL, ATTR_MOBILE, ATTR_TELEPHONE_NUMBER, ATTR_THUMBNAIL_PHOTO, ATTR_PHOTO };
    private static final String[] AUTHENTICATE_RETURN_ATTRS = { ATTR_SAM_ACCOUNT_NAME, ATTR_NAME, ATTR_MAIL, ATTR_MOBILE, ATTR_TELEPHONE_NUMBER, ATTR_THUMBNAIL_PHOTO, ATTR_PHOTO };

    // @todo - use a business exception
    public Person authenticate(String username, String plainTextPassword) throws Exception {

        try {
            String searchFilter = "(&(objectClass=user)(" + ATTR_SAM_ACCOUNT_NAME + "=" + username + "))";
            NamingEnumeration<SearchResult> sr = performLdapSearch(username, plainTextPassword, searchFilter, AUTHENTICATE_RETURN_ATTRS);
            SearchResult searchResult = (sr.hasMore()) ? sr.next() : null;
            if (searchResult == null) {
                throw new Exception("unable to find requested user");
            }
            if (!username.equals(getAttributeValue(searchResult.getAttributes(), ATTR_SAM_ACCOUNT_NAME))) {
                throw new Exception("unable to find requested user");
            }

            return buildPerson(searchResult);

        } catch (CommunicationException cex) {
            // @todo - use a logger
            System.out.println("Authentication Server is not reachable");
            // @todo - use a business exception
            throw new Exception("Authentication server could not be reached. Please check your network connection and try again.", cex);
        } catch (AuthenticationException aex) {
            // @todo - use a logger
            System.out.println("Authentication failed!");
            // @todo - use a business exception
            throw new Exception(aex);
        } catch (NamingException nex) {
            // @todo - use a logger
            System.out.println("Something went wrong!");
            nex.printStackTrace();
            // @todo - use a business exception
            throw new Exception(nex);
        }
    }

    public List<Person> search(String username, String plainTextPassword, String querystring) throws Exception {
        try {
            String searchPattern = "*" + querystring.replaceAll("\\s+", "*") + "*";

            StringBuilder attributSearch = new StringBuilder("(|");
            for (String att : SEARCHABLE_ATTRS) {
                attributSearch.append("(").append(att).append("=").append(searchPattern).append(")");
            }
            attributSearch.append(")");

            String searchFilter = "(&(objectClass=user)" + attributSearch + ")";
            NamingEnumeration<SearchResult> ldapResult = performLdapSearch(username, plainTextPassword, searchFilter, SEARCH_RETURN_ATTRS);

            List<Person> result = new ArrayList<>();
            while (ldapResult.hasMoreElements()) {
                SearchResult sr = ldapResult.next();
                result.add(buildPerson(sr));

            }

            return result;

        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    private Person buildPerson(SearchResult sr) throws NamingException {
        Attributes attributes = sr.getAttributes();
        String name = getAttributeValue(attributes, ATTR_NAME);
        String phone = getAttributeValue(attributes, ATTR_MOBILE);
        if (phone == null) {
            phone = getAttributeValue(attributes, ATTR_TELEPHONE_NUMBER);
        }
        String mail = getAttributeValue(attributes, ATTR_MAIL);
        byte[] photo = getAttributeValue(attributes, ATTR_THUMBNAIL_PHOTO);
        if (photo == null) {
            photo = getAttributeValue(attributes, ATTR_PHOTO);
        }
        String photoBase64 = null;
        if (photo != null) {
            photoBase64 = Base64.encodeBase64String(photo);
        }

        Person person = new Person();
        person.setName(name);
        person.setPhone(phone);
        person.setEmail(mail);
        person.setPhoto(photoBase64);
        return person;
    }

    @SuppressWarnings("unchecked")
    private <T> T getAttributeValue(Attributes attributes, String attrID) throws NamingException {
        Attribute attribute = attributes.get(attrID);
        if (attribute != null) {
            return (T) attribute.get();
        }

        return null;
    }

    private NamingEnumeration<SearchResult> performLdapSearch(String username, String plainTextPassword, String searchFilter, String[] attrIDs) throws NamingException {
        InitialDirContext ctx = getInitialContext(username, plainTextPassword);

        SearchControls ctls = new SearchControls();
        ctls.setReturningAttributes(attrIDs);
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        NamingEnumeration<SearchResult> sr = ctx.search("", searchFilter, ctls);
        return sr;
    }

    private InitialDirContext getInitialContext(String username, String plainTextPassword) throws NamingException {
        Hashtable<String, String> authEnv = new Hashtable<String, String>(11);
        String dn = "glintt\\" + username;
        String ldapURL = "ldap://gpdc02.glintt.com:389/DC=glintt,DC=com?sAMAccountName?sub?(objectClass=*)";

        authEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        authEnv.put(Context.PROVIDER_URL, ldapURL);
        authEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
        authEnv.put(Context.SECURITY_PRINCIPAL, dn);
        if (plainTextPassword == null) {
            plainTextPassword = "";
        }
        authEnv.put(Context.SECURITY_CREDENTIALS, plainTextPassword);
        InitialDirContext ctx = new InitialDirContext(authEnv);
        return ctx;
    }

}
